﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using HippoMapper.Entity.Base;

namespace HippoMapper.MapperService
{
    public class EntityQuery<T> where T:IEntity
    {
        Queue<EntityParameter> parameters = new Queue<EntityParameter>();

        public IEnumerable<EntityParameter> Parameters
        {
            get { return parameters; }
        }

        public string Process(Expression<Func<T, bool>> expression)
        {
            return ProcessExpression(expression);
        }

        private string ProcessExpression(Expression expression)
        {
            if (expression is LambdaExpression)
                return ProcessExpression(((LambdaExpression) expression).Body);

            if (expression is BinaryExpression)
                return ProcessBinaryExpression((BinaryExpression)expression);

            if (expression is MemberExpression)
                return ProcessMemberExpression((MemberExpression)expression);

            //if (expression is ConstantExpression)
            //    return ProcessConstantExpression((ConstantExpression) expression);

            return null;
            
        }

        private string ProcessBinaryExpression(BinaryExpression expression)
        {
            string operation = null;

            switch (expression.NodeType)
            {
                case ExpressionType.Equal:
                    operation = "=";
                    break;

                case ExpressionType.GreaterThan:
                    operation = ">";
                    break;

                case ExpressionType.GreaterThanOrEqual:
                    operation = ">=";
                    break;

                case ExpressionType.LessThan:
                    operation = "<";
                    break;

                case ExpressionType.LessThanOrEqual:
                    operation = "<=";
                    break;

                case ExpressionType.NotEqual:
                    operation = "<>";
                    break;

                case ExpressionType.OrElse:
                    operation = " OR ";
                    break;

                case ExpressionType.AndAlso:
                    operation = " AND ";
                    break;
            }

            if (expression.Left is MemberExpression)
            {
                string preProcessRightValue = ProcessSpeicalRightValue((MemberExpression)expression.Left, operation, expression.Right);

                if (preProcessRightValue != null)
                    return preProcessRightValue;

                StringBuilder sb = new StringBuilder();

                ParameterExpression parameterExpression = ((MemberExpression)expression.Left).Expression as ParameterExpression;

                MemberInfo member = ((MemberExpression)expression.Left).Member;

                Type ownerType = member.DeclaringType;

                IDictionary<string, PropertyInfoAttribute> propertyColumnMap = MapperService.PropertyColumnMap;

                IDictionary<string, EntityInfoAttribute> entityTableMap = MapperService.EntityTableMap;

                if (!entityTableMap.ContainsKey(ownerType.FullName))
                {
                    throw new NullReferenceException("No such entity map info:" + ownerType.FullName);
                }

                if (!propertyColumnMap.ContainsKey(ownerType.FullName + "." + member.Name))
                {
                    throw new NullReferenceException("No such entity property map info:" + ownerType.FullName + "." + member.Name);
                }

                PropertyInfoAttribute propertyInfo = propertyColumnMap[ownerType.FullName + "." + member.Name];

                string leftStr = "[" + entityTableMap[ownerType.FullName].TableName + "].[" + propertyInfo.Field + "]";

                if (expression.Right is ConstantExpression)
                {
                    string rightStr = "@" + member.Name.ToUpper();

                    EntityParameter parameter = new EntityParameter
                    {
                        ParameterName = rightStr,
                        Direction = System.Data.ParameterDirection.Input,
                        Value = ((ConstantExpression)expression.Right).Value,
                        DbType = propertyInfo.DataType
                    };

                    parameters.Enqueue(parameter);

                    sb.Append(leftStr).Append(operation).Append(rightStr);

                    return sb.ToString();
                }

                else
                {
                    string rightStr = ProcessExpression(expression.Right);

                    sb.Append(leftStr).Append(operation).Append(rightStr);

                    return sb.ToString();
                }
            }

            else
            {
                string left = ProcessExpression(expression.Left);

                string right = ProcessExpression(expression.Right);

                return left + operation + right;
            }

            return null;
        }

        private string ProcessMemberExpression(MemberExpression expression)
        {
            ParameterExpression parameterExpression = expression.Expression as ParameterExpression;

            MemberInfo member = expression.Member;

            Type ownerType = member.DeclaringType;

            IDictionary<string, PropertyInfoAttribute> propertyColumnMap = MapperService.PropertyColumnMap;

            IDictionary<string, EntityInfoAttribute> entityTableMap = MapperService.EntityTableMap;

            if(!entityTableMap.ContainsKey(ownerType.FullName))
            {
                throw new NullReferenceException("No such entity map info:" + ownerType.FullName);
            }

            if(!propertyColumnMap.ContainsKey(ownerType.FullName + "." + member.Name))
            {
                throw new NullReferenceException("No such entity property map info:" + ownerType.FullName + "." + member.Name);
            }

            PropertyInfoAttribute propertyInfo = propertyColumnMap[ownerType.FullName + "." + member.Name];

            return "[" + entityTableMap[ownerType.FullName].TableName + "].[" + propertyInfo.Field + "]";
        }

        private string ProcessSpeicalRightValue(MemberExpression leftExpression, string operation, Expression rightExpression)
        {
            if (!(rightExpression is MethodCallExpression))
                return null;

            MethodCallExpression methodCallExpression = (MethodCallExpression)rightExpression;

            MethodInfo method = methodCallExpression.Method;

            if (method.DeclaringType.Equals(typeof(DbMethod)))
            {
                switch (method.Name)
                {
                    case "GETDATE":

                        return ProcessMemberExpression(leftExpression) + operation + "GETDATE()";

                        break;
                }
            }

            return null;
        }

        //private string ProcessConstantExpression(ConstantExpression expression)
        //{
        //    string result = "@" + expression.Value.ToString().Replace(" ", "_").ToUpper();
        //}

    }
}
